package com.dhcc.bpm.modules.auth.controller;

import com.alibaba.fastjson.JSONObject;
import com.dhcc.bpm.common.annotation.Log;
import com.dhcc.bpm.common.cache.CacheNames;
import com.dhcc.bpm.common.constant.CommonConstant;
import com.dhcc.bpm.common.enums.BusinessType;
import com.dhcc.bpm.common.manager.AsyncManager;
import com.dhcc.bpm.common.manager.factory.AsyncFactory;
import com.dhcc.bpm.common.utils.AES2Util;
import com.dhcc.bpm.common.utils.DateUtils;
import com.dhcc.bpm.modules.auth.granter.ITokenGranter;
import com.dhcc.bpm.modules.auth.granter.TokenGranterBuilder;
import com.dhcc.bpm.modules.auth.granter.TokenParameter;
import com.dhcc.bpm.modules.auth.utils.TokenUtil;
import com.dhcc.bpm.modules.scf.util.OBSUtil;
import com.dhcc.bpm.modules.system.entity.Param;
import com.dhcc.bpm.modules.system.entity.Role;
import com.dhcc.bpm.modules.system.entity.User;
import com.dhcc.bpm.modules.system.entity.UserInfo;
import com.dhcc.bpm.modules.system.service.IDeptService;
import com.dhcc.bpm.modules.system.service.IParamService;
import com.dhcc.bpm.modules.system.service.IRoleService;
import com.dhcc.bpm.modules.system.service.IUserService;
import com.dhcc.bpm.modules.system.vo.DeptVO2;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import com.wf.captcha.ArithmeticCaptcha;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.core.launch.constant.AppConstant;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.log.annotation.ApiLog;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.support.Kv;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName AuthController
 * @Description 认证模块
 * @Author sjm
 * @Date 2020/2/21 0021 15:16
 * @Version V1.0
 * @Copyright: 2020 东华软件 . All rights reserved.
 **/
@RestController
@AllArgsConstructor
@RequestMapping(AppConstant.APPLICATION_AUTH_NAME)
@ApiSort(1)
@Api(value = "用户授权认证", tags = "授权接口")
public class AuthController {
    private final static Logger log = LoggerFactory.getLogger(AuthController.class);

    private StringRedisTemplate stringRedisTemplate;

    private IUserService userService;
    private IRoleService roleService;
    private IParamService paramService;
    private IDeptService deptService;


    private final OBSUtil obsUtil;

    //用户登录次数计数  redisKey 前缀
    private final static String SHIRO_LOGIN_COUNT = "shiro_login_count_";

    //用户登录是否被锁定  redisKey 前缀
    private final static String SHIRO_IS_LOCK = "shiro_is_lock_";

    //用户登录角色  redisKey 前缀
    private final static String SHIRO_ROLE_ID = "shiro_role_id_";

    @ApiLog("登录用户验证")
    @PostMapping("/oauth/token")
    @ApiOperation(value = "获取认证token", notes = "传入租户ID:tenantId,账号:account,密码:password,角色:role")
    @Log(title = "登录用户验证", businessType = BusinessType.GRANT)
    public Kv token(
//			@ApiParam(value = "租户ID", required = true) @RequestParam(defaultValue = "000000", required = false) String tenantId,
//			@ApiParam(value = "账号", required = true) @RequestParam(required = false) String username,
//			@ApiParam(value = "密码", required = true) @RequestParam(required = false) String password,
//			@ApiParam(value = "角色", required = true) @RequestParam(required = false) String role) {
            @RequestBody JSONObject json) {
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        Kv authInfo = Kv.create();

        String tenantId = json.getString("tenantId") == null ? "000000" : json.getString("tenantId");
        String username = json.getString("username");
        String password = json.getString("password");
        String role = json.getString("role");
        String grantType = json.getString("grant_type");
        String refreshToken = json.getString("refresh_token");

        //管理端登录增加验证码
        if (TokenUtil.GRANT_TYPE.equals(grantType)) {
            HttpServletRequest request = WebUtil.getRequest();
            String key = request.getHeader(TokenUtil.CAPTCHA_HEADER_KEY);
            String code = json.getString("code");
            // 获取验证码
            String redisCode = String.valueOf(opsForValue.get(CacheNames.CAPTCHA_KEY + key));
            stringRedisTemplate.delete(CacheNames.CAPTCHA_KEY + key);
            // 判断验证码
            if (code == null || !StringUtil.equalsIgnoreCase(redisCode, code)) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "验证码错误，请重新输入！"));
                return authInfo.set("error_code", HttpServletResponse.SC_FORBIDDEN).set("error_description", "验证码错误，请重新输入！");
            }
        }

        // 解密密码
        try {
            Param param = paramService.getParamByKey(CommonConstant.PASS_SECRET);
            String decryptKey = param == null ? CommonConstant.INIT_SECRET : param.getParamValue();
            password = AES2Util.decrypt(password, decryptKey);
        } catch (Exception e) {
            log.error("解密密码异常{}", e);
        }
        if (username == null || password == null) {
            return authInfo.set("error_code", HttpServletResponse.SC_BAD_REQUEST).set("error_description", "账号或密码不能为空！");
        }

//		String grantType = WebUtil.getRequest().getParameter("grant_type");
//		String refreshToken = WebUtil.getRequest().getParameter("refresh_token");

        log.info("用户:{}，登陆的次数是:{}", username, opsForValue.get(SHIRO_LOGIN_COUNT + username));
        if (role == null || "".equals(role)) {
            String roleId = opsForValue.get(SHIRO_ROLE_ID + username);
            if (roleId == null || "".equals(roleId)) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "未获得用户的角色信息"));
                return authInfo.set("error_code", HttpServletResponse.SC_FORBIDDEN).set("error_description", "未获得用户的角色信息");
            } else {
                role = roleId;
            }
        }
        //如果这个账号登陆异常，则在登陆页面提醒。
        if (opsForValue.get(SHIRO_LOGIN_COUNT + username) != null) {
            if (Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT + username)) >= 3) {
                if ("LOCK".equals(opsForValue.get(SHIRO_IS_LOCK + username))) {
                    //计数大于3次，设置用户被锁定一分钟
                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "账号或密码输入错误超过3次，帐号10分钟内禁止登录！"));
                    return authInfo.set("error_code", HttpServletResponse.SC_BAD_REQUEST).set("error_description", "账号或密码输入错误超过3次，帐号10分钟内禁止登录！");
                } else {
                    //清空登录计数
                    opsForValue.set(SHIRO_LOGIN_COUNT + username, "0");
                }
            }
        }
        String userType = Func.toStr(WebUtil.getRequest().getHeader(TokenUtil.USER_TYPE_HEADER_KEY), TokenUtil.DEFAULT_USER_TYPE);

        TokenParameter tokenParameter = new TokenParameter();
        tokenParameter.getArgs().set("tenantId", tenantId).set("username", username).set("password", password).set("grantType", grantType).set("refreshToken", refreshToken).set("userType", userType);

        ITokenGranter granter = TokenGranterBuilder.getGranter(grantType);
        UserInfo userInfo = granter.grant(tokenParameter);
        User user = userInfo.getUser();
        String avatar = user.getAvatar();
        String downloadUrl = obsUtil.getDownloadUrl(avatar);
        if(downloadUrl !=null ){
            user.setAvatar(downloadUrl);
        }

        userInfo.setUser(user);

        List<DeptVO2> deptList=deptService.queryDeptByUserId(user.getId());
        List<DeptVO2> deptAll=deptService.queryDeptByUserId();
        userInfo.setDepts( deptList);
        userInfo.setDeptAll( deptAll);
        //TODO

        if (userInfo == null || userInfo.getUser() == null) {
            //访问一次，计数一次
            opsForValue.increment(SHIRO_LOGIN_COUNT + username, 1);  //每次增加1
            log.info("用户:{}，登陆的次数是:{}", username, opsForValue.get(SHIRO_LOGIN_COUNT + username));
            //实现锁定
            if (Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT + username)) >= 3) {
                opsForValue.set(SHIRO_IS_LOCK + username, "LOCK");  //锁住这个账号，值是LOCK。
                stringRedisTemplate.expire(SHIRO_IS_LOCK + username, 10, TimeUnit.MINUTES);  //expire  变量存活期限
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "账号或密码输入错误3次，帐号10分钟内禁止登录！"));
                return authInfo.set("error_code", HttpServletResponse.SC_BAD_REQUEST).set("error_description", "账号或密码输入错误3次，帐号10分钟内禁止登录！");
            } else {
                int num = 3 - Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT + username));
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "用户名或密码不正确"));
                return authInfo.set("error_code", HttpServletResponse.SC_BAD_REQUEST).set("error_description", "用户名或密码不正确,再输错" + num + "次该用户将被锁定10分钟。");
            }
        }

        if (Func.isEmpty(userInfo.getRoles())) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "未获得用户的角色信息"));
            return authInfo.set("error_code", HttpServletResponse.SC_BAD_REQUEST).set("error_description", "未获得用户的角色信息");
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_SUCCESS, "登录成功"));
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        String passdate = df.format(userInfo.getUser().getUpdateTime());
        log.info("用户密码最后更新日期：{}", passdate);
        int deadLineDay = 0;
        try {
            deadLineDay = Integer.parseInt(DateUtils.pubDeadLineDay(passdate, 90).replace("-", ""));
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        int nowdate = Integer.parseInt(df.format(new Date()).replace("-", ""));
        log.info("现在日期：{}", nowdate);
        if (nowdate > deadLineDay) {
            log.info("密码已经过期");
            userInfo.getUser().setStatus(10);//密码过期临时状态
        }
        //清空登录计数
        opsForValue.set(SHIRO_LOGIN_COUNT + username, "0");
        //清空锁
        opsForValue.set(SHIRO_IS_LOCK + username, "");
        List<Role> roles = roleService.getRoleSelectByIds(role);
        if (roles == null || roles.size() == 0) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "未获得用户的角色信息"));
            return authInfo.set("error_code", HttpServletResponse.SC_FORBIDDEN).set("error_description", "未获得用户的角色信息");
        }
        opsForValue.set(SHIRO_ROLE_ID + username, role);
        Kv auth = TokenUtil.createAuthInfo(userInfo, roles.get(0));
        String token = auth.getStr(TokenConstant.ACCESS_TOKEN);
        //登录token存到redis, 有效期默认60分钟
        opsForValue.set(auth.getStr(TokenConstant.ACCOUNT) + "@token", token, 3600, TimeUnit.SECONDS);
        //删除上一个账号审批菜单权限
//		stringRedisTemplate.delete(auth.getStr(TokenConstant.ACCOUNT) + "@workFlowRole");
        return auth;
    }

    @ApiLog("登录获取用户角色信息")
    @PostMapping("/oauth/role")
    @ApiOperation(value = "获取用户角色信息", notes = "账号:account,密码:password")
    @Log(title = "登录获取用户角色信息", businessType = BusinessType.GRANT)
    public Kv role(
//			@ApiParam(value = "租户ID", required = true) @RequestParam(defaultValue = "000000", required = false) String tenantId,
//			@ApiParam(value = "账号", required = true) @RequestParam(required = false) String username,
//			@ApiParam(value = "密码", required = true) @RequestParam(required = false) String password) {
            @RequestBody JSONObject json) {

        Kv authInfo = Kv.create();

        String tenantId = json.getString("tenantId") == null ? "000000" : json.getString("tenantId");
        String username = json.getString("username");
        String password = json.getString("password");
        String grantType = json.getString("grant_type");
        String refreshToken = json.getString("refresh_token");
        // 解密密码
        try {
            Param param = paramService.getParamByKey(CommonConstant.PASS_SECRET);
            String decryptKey = param == null ? CommonConstant.INIT_SECRET : param.getParamValue();

            password = AES2Util.decrypt(password, decryptKey);
        } catch (Exception e) {
            log.error("解密密码异常{}", e);
        }
        if (username == null || password == null) {
            return authInfo.set("error_code", HttpServletResponse.SC_BAD_REQUEST).set("error_description", "账号或密码不能为空！");
        }

//		String grantType = WebUtil.getRequest().getParameter("grant_type");
//		String refreshToken = WebUtil.getRequest().getParameter("refresh_token");
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        log.info("用户:{}，登陆的次数是:{}", username, opsForValue.get(SHIRO_LOGIN_COUNT + username));

        //如果这个账号登陆异常，则在登陆页面提醒。
        if (opsForValue.get(SHIRO_LOGIN_COUNT + username) != null) {
            if (Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT + username)) >= 10) {
                if ("LOCK".equals(opsForValue.get(SHIRO_IS_LOCK + username))) {
                    //计数大于3次，设置用户被锁定一分钟
                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "账号或密码输入错误超过10次，帐号1分钟内禁止登录！"));
                    return authInfo.set("error_code", HttpServletResponse.SC_BAD_REQUEST).set("error_description", "账号或密码输入错误超过10次，帐号1分钟内禁止登录！");
                } else {
                    //清空登录计数
                    opsForValue.set(SHIRO_LOGIN_COUNT + username, "0");
                }
            }
        }
        String userType = Func.toStr(WebUtil.getRequest().getHeader(TokenUtil.USER_TYPE_HEADER_KEY), TokenUtil.DEFAULT_USER_TYPE);

        TokenParameter tokenParameter = new TokenParameter();
        tokenParameter.getArgs().set("tenantId", tenantId).set("username", username).set("password", password).set("grantType", grantType).set("refreshToken", refreshToken).set("userType", userType);

        ITokenGranter granter = TokenGranterBuilder.getGranter(grantType);
        UserInfo userInfo = granter.grant(tokenParameter);

        if (userInfo == null || userInfo.getUser() == null) {
            //访问一次，计数一次
            opsForValue.increment(SHIRO_LOGIN_COUNT + username, 1);  //每次增加1
            log.info("用户:{}，登陆的次数是:{}", username, opsForValue.get(SHIRO_LOGIN_COUNT + username));
            //实现锁定
            if (Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT + username)) >= 10) {
                opsForValue.set(SHIRO_IS_LOCK + username, "LOCK");  //锁住这个账号，值是LOCK。
                stringRedisTemplate.expire(SHIRO_IS_LOCK + username, 1, TimeUnit.MINUTES);  //expire  变量存活期限
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "账号或密码输入错误10次，帐号1分钟内禁止登录！"));
                return authInfo.set("error_code", HttpServletResponse.SC_BAD_REQUEST).set("error_description", "账号或密码输入错误10次，帐号1分钟内禁止登录！");
            } else {
                int num = 10 - Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT + username));
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "用户名或密码不正确"));
                return authInfo.set("error_code", HttpServletResponse.SC_BAD_REQUEST).set("error_description", "用户名或密码不正确,再输错" + num + "次该用户将被锁定1分钟。");
            }
        }

        if (Func.isEmpty(userInfo.getRoles())) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "未获得用户的角色信息"));
            return authInfo.set("error_code", HttpServletResponse.SC_BAD_REQUEST).set("error_description", "未获得用户的角色信息");
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_SUCCESS, "登录成功"));
        //清空登录计数
        opsForValue.set(SHIRO_LOGIN_COUNT + username, "0");
        //清空锁
        opsForValue.set(SHIRO_IS_LOCK + username, "");
        Kv role = Kv.create();
        List<Role> roles = roleService.getRoleSelectByIds(userInfo.getUser().getRoleId());
        role.set("roles", roles);
        return role;
    }

    @ApiLog("生成客户端token")
    @GetMapping("/oauth/createToken")
    @ApiOperation(value = "生成认证token", notes = "")
    @Log(title = "生成客户端token", businessType = BusinessType.GRANT)
    public Kv createToken(HttpServletRequest request) {
        UserInfo userInfo = new UserInfo();
        User user = new User();
        BladeUser bladeUser = AuthUtil.getUser(request);
        BeanUtils.copyProperties(bladeUser, user);
        userInfo.setUser(user);
        List<Role> roles = roleService.getRoleSelectByIds(bladeUser.getRoleId());
        return TokenUtil.createAuthInfo(userInfo, roles.get(0));
    }

    @Log(title = "单点登录获取用户角色信息", businessType = BusinessType.GRANT)
    @ApiLog("单点登录获取用户角色信息")
    @PostMapping("/oauth/getRole")
    @ApiOperation(value = "单点获取用户角色信息")
    public Kv getRole(HttpServletRequest request) {
        String ssoUserName = request.getHeader("USERNAME");
        String username = ssoUserName;
        Kv authInfo = Kv.create();
        User user = userService.selectUserByaccount(username);
        UserInfo userInfo = userService.userInfo(user.getId());
        if (Func.isEmpty(userInfo.getRoles())) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "未获得用户的角色信息"));
            return authInfo.set("error_code", HttpServletResponse.SC_BAD_REQUEST).set("error_description", "未获得用户的角色信息");
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_SUCCESS, "登录成功"));
        Kv role = Kv.create();
        List<Role> roles = roleService.getRoleSelectByIds(userInfo.getUser().getRoleId());
        role.set("roles", roles);
        return role;
    }

    @ApiLog("单点登录用户验证")
    @Log(title = "单点登录用户验证", businessType = BusinessType.GRANT)
    @PostMapping("/oauth/getToken")
    @ApiOperation(value = "单点获取认证token")
    public Kv getToken(HttpServletRequest request, @RequestParam(required = false) String username, @ApiParam(value = "角色", required = true) @RequestParam(required = false) String role) {
        String ssoUserName = request.getHeader("USERNAME");
        // String username = ssoUserName;
        Kv authInfo = Kv.create();
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        User user = userService.selectUserByaccount(username);
        UserInfo userInfo = userService.userInfo(user.getId());
        if (Func.isEmpty(userInfo.getRoles())) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "未获得用户的角色信息"));
            return authInfo.set("error_code", HttpServletResponse.SC_BAD_REQUEST).set("error_description", "未获得用户的角色信息");
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_SUCCESS, "登录成功"));
        List<Role> roles = roleService.getRoleSelectByIds(role);
        if (roles == null || roles.size() == 0) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, CommonConstant.LOGIN_FAIL, "未获得用户的角色信息"));
            return authInfo.set("error_code", HttpServletResponse.SC_FORBIDDEN).set("error_description", "未获得用户的角色信息");
        }
        opsForValue.set(SHIRO_ROLE_ID + username, role);
        return TokenUtil.createAuthInfo(userInfo, roles.get(0));
    }

    @ApiLog("退出登录")
    @Log(title = "退出登录", businessType = BusinessType.GRANT)
    @PostMapping("/oauth/logout")
    @ApiOperation(value = "退出登录")
    public R logout(HttpServletRequest request) {
        BladeUser bladeUser = AuthUtil.getUser(request);
        if (bladeUser != null) {
            ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
            //redis存储的token
            String authToken = opsForValue.get(bladeUser.getAccount() + "@token");
            //请求token
            String token = request.getHeader("Blade-Auth");
            if (token == null) {
                token = request.getParameter("Blade-Auth");
            }
            //判断是否为后登录用户退出登录，后登录用户退出登录须删除token，先登录用户token被覆盖无须删除
            if (token.replace("bearer ", "").equals(authToken)) {
                stringRedisTemplate.delete(bladeUser.getAccount() + "@token");//根据key删除缓存
                //工作流校验用户是否拥有流程设计权限
                stringRedisTemplate.delete(bladeUser.getAccount() + "@workFlowRole");
            }
        }
        return R.success("退出登录成功！");
    }


    @GetMapping("/captcha")
    @ApiOperation(value = "获取验证码")
    public R<Kv> captcha() {
        // 算术类型
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(110, 48);
        captcha.setLen(3);  // 几位数运算，默认是两位
        captcha.getArithmeticString();  // 获取运算的公式：3+2=?
        captcha.text();  // 获取运算的结果：5

        String verCode = captcha.text().toLowerCase();
        String key = UUID.randomUUID().toString();
        // 存入redis并设置过期时间为30分钟
        stringRedisTemplate.opsForValue().set(CacheNames.CAPTCHA_KEY + key, verCode, 5L, TimeUnit.MINUTES);
        // 将key和base64返回给前端
        return R.data(Kv.create().set("key", key).set("image", captcha.toBase64()));
    }
}
